#include "stdafx.h"

#include "URI.h"

#include "StrUtils.h"
#include "NumberUtils.h"

const std::string URI::RESERVED_PATH     = "?#";
const std::string URI::RESERVED_QUERY    = "#";
const std::string URI::RESERVED_FRAGMENT = "";
const std::string URI::ILLEGAL           = "%<>{}|\\\"^`";


URI::URI() : m_port(0)
{
}


URI::URI(const std::string& uri):
	m_port(0)
{
	Parse(uri);
}


URI::URI(const char* uri):
	m_port(0)
{
	Parse(std::string(uri));
}

	
URI::URI(const std::string& scheme, const std::string& pathEtc):
	m_scheme(scheme),
	m_port(0)
{
	toLowerInPlace(m_scheme);
	m_port = GetWellKnownPort();
	std::string::const_iterator beg = pathEtc.begin();
	std::string::const_iterator end = pathEtc.end();
	ParsePathEtc(beg, end);
}

	
URI::URI(const std::string& scheme, const std::string& authority, const std::string& pathEtc):
	m_scheme(scheme)
{
	toLowerInPlace(m_scheme);
	std::string::const_iterator beg = authority.begin();
	std::string::const_iterator end = authority.end();
	ParseAuthority(beg, end);
	beg = pathEtc.begin();
	end = pathEtc.end();
	ParsePathEtc(beg, end);
}


URI::URI(const std::string& scheme, const std::string& authority, const std::string& path, const std::string& query):
	m_scheme(scheme),
	m_path(path),
	m_query(query)
{
	toLowerInPlace(m_scheme);
	std::string::const_iterator beg = authority.begin();
	std::string::const_iterator end = authority.end();
	ParseAuthority(beg, end);
}


URI::URI(const std::string& scheme, const std::string& authority, const std::string& path, const std::string& query, const std::string& fragment):
	m_scheme(scheme),
	m_path(path),
	m_query(query),
	m_fragment(fragment)
{
	toLowerInPlace(m_scheme);
	std::string::const_iterator beg = authority.begin();
	std::string::const_iterator end = authority.end();
	ParseAuthority(beg, end);
}


URI::URI(const URI& uri):
	m_scheme(uri.m_scheme),
	m_userInfo(uri.m_userInfo),
	m_host(uri.m_host),
	m_port(uri.m_port),
	m_path(uri.m_path),
	m_query(uri.m_query),
	m_fragment(uri.m_fragment)
{
}

	
URI::URI(const URI& baseURI, const std::string& relativeURI):
	m_scheme(baseURI.m_scheme),
	m_userInfo(baseURI.m_userInfo),
	m_host(baseURI.m_host),
	m_port(baseURI.m_port),
	m_path(baseURI.m_path),
	m_query(baseURI.m_query),
	m_fragment(baseURI.m_fragment)
{
	Resolve(relativeURI);
}


URI::~URI()
{
}


URI& URI::operator = (const URI& uri)
{
	if (&uri != this)
	{
		m_scheme   = uri.m_scheme;
		m_userInfo = uri.m_userInfo;
		m_host     = uri.m_host;
		m_port     = uri.m_port;
		m_path     = uri.m_path;
		m_query    = uri.m_query;
		m_fragment = uri.m_fragment;
	}
	return *this;
}

	
URI& URI::operator = (const std::string& uri)
{
	Clear();
	Parse(uri);
	return *this;
}


URI& URI::operator = (const char* uri)
{
	Clear();
	Parse(std::string(uri));
	return *this;
}


void URI::Swap(URI& uri)
{
	std::swap(m_scheme, uri.m_scheme);
	std::swap(m_userInfo, uri.m_userInfo);
	std::swap(m_host, uri.m_host);
	std::swap(m_port, uri.m_port);
	std::swap(m_path, uri.m_path);
	std::swap(m_query, uri.m_query);
	std::swap(m_fragment, uri.m_fragment);
}


void URI::Clear()
{
	m_scheme.clear();
	m_userInfo.clear();
	m_host.clear();
	m_port = 0;
	m_path.clear();
	m_query.clear();
	m_fragment.clear();
}


std::string URI::ToString() const
{
	std::string uri;
	if (IsRelative())
	{
		Encode(m_path, RESERVED_PATH, uri);
	}
	else
	{
		uri = m_scheme;
		uri += ':';
		std::string auth = GetAuthority();
		if (!auth.empty() || m_scheme == "file")
		{
			uri.append("//");
			uri.append(auth);
		}
		if (!m_path.empty())
		{
			if (!auth.empty() && m_path[0] != '/')
				uri += '/';
			Encode(m_path, RESERVED_PATH, uri);
		}
	}
	if (!m_query.empty())
	{
		uri += '?';
		uri.append(m_query);
	}
	if (!m_fragment.empty())
	{
		uri += '#';
		Encode(m_fragment, RESERVED_FRAGMENT, uri);
	}
	return uri;
}


void URI::SetScheme(const std::string& scheme)
{
	m_scheme = scheme;
	toLowerInPlace(m_scheme);
	if (m_port == 0)
		m_port = GetWellKnownPort();
}

	
void URI::SetUserInfo(const std::string& userInfo)
{
	m_userInfo.clear();
	Decode(userInfo, m_userInfo);
}

	
void URI::SetHost(const std::string& host)
{
	m_host = host;
}


unsigned short URI::GetPort() const
{
	if (m_port == 0)
		return GetWellKnownPort();
	else
		return m_port;
}


void URI::SetPort(unsigned short port)
{
	m_port = port;
}

	
std::string URI::GetAuthority() const
{
	std::string auth;
	if (!m_userInfo.empty())
	{
		auth.append(m_userInfo);
		auth += '@';
	}
	if (m_host.find(':') != std::string::npos)
	{
		auth += '[';
		auth += m_host;
		auth += ']';
	}
	else auth.append(m_host);
	if (m_port && !IsWellKnownPort())
	{
		auth += ':';
		AppendNumber(auth, m_port);
	}
	return auth;
}

	
void URI::SetAuthority(const std::string& authority)
{
	m_userInfo.clear();
	m_host.clear();
	m_port = 0;
	std::string::const_iterator beg = authority.begin();
	std::string::const_iterator end = authority.end();
	ParseAuthority(beg, end);
}

	
void URI::SetPath(const std::string& path)
{
	m_path.clear();
	Decode(path, m_path);
}

	
void URI::SetRawQuery(const std::string& query)
{
	m_query = query;
}


void URI::SetQuery(const std::string& query)
{
	m_query.clear();
	Encode(query, RESERVED_QUERY, m_query);
}


std::string URI::GetQuery() const
{
	std::string query;
	Decode(m_query, query);
	return query;
}


void URI::SetFragment(const std::string& fragment)
{
	m_fragment.clear();
	Decode(fragment, m_fragment);
}


void URI::SetPathEtc(const std::string& pathEtc)
{
	m_path.clear();
	m_query.clear();
	m_fragment.clear();
	std::string::const_iterator beg = pathEtc.begin();
	std::string::const_iterator end = pathEtc.end();
	ParsePathEtc(beg, end);
}

	
std::string URI::GetPathEtc() const
{
	std::string pathEtc;
	Encode(m_path, RESERVED_PATH, pathEtc);
	if (!m_query.empty())
	{
		pathEtc += '?';
		Encode(m_query, RESERVED_QUERY, pathEtc);
	}
	if (!m_fragment.empty())
	{
		pathEtc += '#';
		Encode(m_fragment, RESERVED_FRAGMENT, pathEtc);
	}
	return pathEtc;
}


std::string URI::GetPathAndQuery() const
{
	std::string pathAndQuery;
	Encode(m_path, RESERVED_PATH, pathAndQuery);
	if (!m_query.empty())
	{
		pathAndQuery += '?';
		Encode(m_query, RESERVED_QUERY, pathAndQuery);
	}
	return pathAndQuery;
}

	
void URI::Resolve(const std::string& relativeURI)
{
	URI ParsedURI(relativeURI);
	Resolve(ParsedURI);
}


void URI::Resolve(const URI& relativeURI)
{
	if (!relativeURI.m_scheme.empty())
	{
		m_scheme   = relativeURI.m_scheme;
		m_userInfo = relativeURI.m_userInfo;
		m_host     = relativeURI.m_host;
		m_port     = relativeURI.m_port;
		m_path     = relativeURI.m_path;
		m_query    = relativeURI.m_query;
		RemoveDotSegments();
	}
	else
	{
		if (!relativeURI.m_host.empty())
		{
			m_userInfo = relativeURI.m_userInfo;
			m_host     = relativeURI.m_host;
			m_port     = relativeURI.m_port;
			m_path     = relativeURI.m_path;
			m_query    = relativeURI.m_query;
			RemoveDotSegments();
		}
		else
		{
			if (relativeURI.m_path.empty())
			{
				if (!relativeURI.m_query.empty())
					m_query = relativeURI.m_query;
			}
			else
			{
				if (relativeURI.m_path[0] == '/')
				{
					m_path = relativeURI.m_path;
					RemoveDotSegments();
				}
				else
				{
					MergePath(relativeURI.m_path);
				}
				m_query = relativeURI.m_query;
			}
		}
	}
	m_fragment = relativeURI.m_fragment;	
}


bool URI::IsRelative() const
{
	return m_scheme.empty();
}


bool URI::Empty() const
{
	return m_scheme.empty() && m_host.empty() && m_path.empty() && m_query.empty() && m_fragment.empty();
}

	
bool URI::operator == (const URI& uri) const
{
	return Equals(uri);
}


bool URI::operator == (const std::string& uri) const
{
	URI ParsedURI(uri);
	return Equals(ParsedURI);
}


bool URI::operator != (const URI& uri) const
{
	return !Equals(uri);
}


bool URI::operator != (const std::string& uri) const
{
	URI ParsedURI(uri);
	return !Equals(ParsedURI);
}


bool URI::Equals(const URI& uri) const
{
	return m_scheme   == uri.m_scheme
	    && m_userInfo == uri.m_userInfo
	    && m_host     == uri.m_host
	    && GetPort() == uri.GetPort()
	    && m_path     == uri.m_path
	    && m_query    == uri.m_query
	    && m_fragment == uri.m_fragment;
}

	
void URI::Normalize()
{
	RemoveDotSegments(!IsRelative());
}


void URI::RemoveDotSegments(bool removeLeading)
{
	if (m_path.empty()) return;
	
	bool leadingSlash  = *(m_path.begin()) == '/';
	bool trailingSlash = *(m_path.rbegin()) == '/';
	std::vector<std::string> segments;
	std::vector<std::string> normalizedSegments;
	GetPathSegments(segments);
	for (std::vector<std::string>::const_iterator it = segments.begin(); it != segments.end(); ++it)
	{
		if (*it == "..")
		{
			if (!normalizedSegments.empty())
			{
				if (normalizedSegments.back() == "..")
					normalizedSegments.push_back(*it);
				else
					normalizedSegments.pop_back();
			}
			else if (!removeLeading)
			{
				normalizedSegments.push_back(*it);
			}
		}
		else if (*it != ".")
		{
			normalizedSegments.push_back(*it);
		}
	}
	BuildPath(normalizedSegments, leadingSlash, trailingSlash);
}


void URI::GetPathSegments(std::vector<std::string>& segments)
{
	GetPathSegments(m_path, segments);
}


void URI::GetPathSegments(const std::string& path, std::vector<std::string>& segments)
{
	std::string::const_iterator it  = path.begin();
	std::string::const_iterator end = path.end();
	std::string seg;
	while (it != end)
	{
		if (*it == '/')
		{
			if (!seg.empty())
			{
				segments.push_back(seg);
				seg.clear();
			}
		}
		else seg += *it;
		++it;
	}
	if (!seg.empty())
		segments.push_back(seg);
}


void URI::Encode(const std::string& str, const std::string& reserved, std::string& encodedStr)
{
	for (std::string::const_iterator it = str.begin(); it != str.end(); ++it)
	{
		char c = *it;
		if ((c >= 'a' && c <= 'z') || 
		    (c >= 'A' && c <= 'Z') || 
		    (c >= '0' && c <= '9') ||
		    c == '-' || c == '_' || 
		    c == '.' || c == '~')
		{
			encodedStr += c;
		}
		else if (c <= 0x20 || c >= 0x7F || ILLEGAL.find(c) != std::string::npos || reserved.find(c) != std::string::npos)
		{
			encodedStr += '%';
			encodedStr += FormatNumberHex((unsigned) (unsigned char) c, 2);
		}
		else encodedStr += c;
	}
}

	
void URI::Decode(const std::string& str, std::string& decodedStr)
{
	std::string::const_iterator it  = str.begin();
	std::string::const_iterator end = str.end();
	while (it != end)
	{
		char c = *it++;
		if (c == '%')
		{
			if (it == end) ThrowException<UriException>("URI encoding: no hex digit following percent sign", str);
			char hi = *it++;
			if (it == end) ThrowException<UriException>("URI encoding: two hex digits must follow percent sign", str);
			char lo = *it++;
			if (hi >= '0' && hi <= '9')
				c = hi - '0';
			else if (hi >= 'A' && hi <= 'F')
				c = hi - 'A' + 10;
			else if (hi >= 'a' && hi <= 'f')
				c = hi - 'a' + 10;
			else ThrowException<UriException>("URI encoding: not a hex digit");
			c *= 16;
			if (lo >= '0' && lo <= '9')
				c += lo - '0';
			else if (lo >= 'A' && lo <= 'F')
				c += lo - 'A' + 10;
			else if (lo >= 'a' && lo <= 'f')
				c += lo - 'a' + 10;
			else ThrowException<UriException>("URI encoding: not a hex digit");
		}
		decodedStr += c;
	}
}


bool URI::IsWellKnownPort() const
{
	return m_port == GetWellKnownPort();
}


unsigned short URI::GetWellKnownPort() const
{
	if (m_scheme == "ftp")
		return 21;
	else if (m_scheme == "ssh")
		return 22;
	else if (m_scheme == "telnet")
		return 23;
	else if (m_scheme == "http")
		return 80;
	else if (m_scheme == "nntp")
		return 119;
	else if (m_scheme == "ldap")
		return 389;
	else if (m_scheme == "https")
		return 443;
	else if (m_scheme == "rtsp")
		return 554;
	else if (m_scheme == "sip")
		return 5060;
	else if (m_scheme == "sips")
		return 5061;
	else if (m_scheme == "xmpp")
		return 5222;
	else
		return 0;
}


void URI::Parse(const std::string& uri)
{
	std::string::const_iterator it  = uri.begin();
	std::string::const_iterator end = uri.end();
	if (it == end) return;
	if (*it != '/' && *it != '.' && *it != '?' && *it != '#')
	{
		std::string scheme;
		while (it != end && *it != ':' && *it != '?' && *it != '#' && *it != '/') scheme += *it++;
		if (it != end && *it == ':')
		{
			++it;
			if (it == end) ThrowException<UriException>("URI scheme must be followed by authority or path", uri);
			SetScheme(scheme);
			if (*it == '/')
			{
				++it;
				if (it != end && *it == '/')
				{
					++it;
					ParseAuthority(it, end);
				}
				else --it;
			}
			ParsePathEtc(it, end);
		}
		else 
		{
			it = uri.begin();
			ParsePathEtc(it, end);
		}
	}
	else ParsePathEtc(it, end);
}


void URI::ParseAuthority(std::string::const_iterator& it, const std::string::const_iterator& end)
{
	std::string userInfo;
	std::string part;
	while (it != end && *it != '/' && *it != '?' && *it != '#')
	{
		if (*it == '@')
		{
			userInfo = part;
			part.clear();
		}
		else part += *it;
		++it;
	}
	std::string::const_iterator pbeg = part.begin();
	std::string::const_iterator pend = part.end();
	ParseHostAndPort(pbeg, pend);
	m_userInfo = userInfo;
}


void URI::ParseHostAndPort(std::string::const_iterator& it, const std::string::const_iterator& end)
{
	if (it == end) return;
	std::string host;
	if (*it == '[')
	{
		// IPv6 address
		++it;
		while (it != end && *it != ']') host += *it++;
		if (it == end) ThrowException<UriException>("unterminated IPv6 address");
		++it;
	}
	else
	{
		while (it != end && *it != ':') host += *it++;
	}
	if (it != end && *it == ':')
	{
		++it;
		std::string port;
		while (it != end) port += *it++;
		if (!port.empty())
		{
			int nport = 0;
			if (TryParseInt(port, nport) && nport > 0 && nport < 65536)
				m_port = (unsigned short) nport;
			else
				ThrowException<UriException>("bad or invalid port number", port);
		}
		else m_port = GetWellKnownPort();
	}
	else m_port = GetWellKnownPort();
	m_host = host;
	toLowerInPlace(m_host);
}


void URI::ParsePath(std::string::const_iterator& it, const std::string::const_iterator& end)
{
	std::string path;
	while (it != end && *it != '?' && *it != '#') path += *it++;
	Decode(path, m_path);
}


void URI::ParsePathEtc(std::string::const_iterator& it, const std::string::const_iterator& end)
{
	if (it == end) return;
	if (*it != '?' && *it != '#')
		ParsePath(it, end);
	if (it != end && *it == '?')
	{
		++it;
		ParseQuery(it, end);
	}
	if (it != end && *it == '#')
	{
		++it;
		ParseFragment(it, end);
	}	
}


void URI::ParseQuery(std::string::const_iterator& it, const std::string::const_iterator& end)
{
	m_query.clear();
	while (it != end && *it != '#') m_query += *it++;
}


void URI::ParseFragment(std::string::const_iterator& it, const std::string::const_iterator& end)
{
	std::string fragment;
	while (it != end) fragment += *it++;
	Decode(fragment, m_fragment);
}


void URI::MergePath(const std::string& path)
{
	std::vector<std::string> segments;
	std::vector<std::string> normalizedSegments;
	bool addLeadingSlash = false;
	if (!m_path.empty())
	{
		GetPathSegments(segments);
		bool endsWithSlash = *(m_path.rbegin()) == '/';
		if (!endsWithSlash && !segments.empty())
			segments.pop_back();
		addLeadingSlash = m_path[0] == '/';
	}
	GetPathSegments(path, segments);
	addLeadingSlash = addLeadingSlash || (!path.empty() && path[0] == '/');
	bool hasTrailingSlash = (!path.empty() && *(path.rbegin()) == '/');
	bool addTrailingSlash = false;
	for (std::vector<std::string>::const_iterator it = segments.begin(); it != segments.end(); ++it)
	{
		if (*it == "..")
		{
			addTrailingSlash = true;
			if (!normalizedSegments.empty())
				normalizedSegments.pop_back();
		}
		else if (*it != ".")
		{
			addTrailingSlash = false;
			normalizedSegments.push_back(*it);
		}
		else addTrailingSlash = true;
	}
	BuildPath(normalizedSegments, addLeadingSlash, hasTrailingSlash || addTrailingSlash);
}


void URI::BuildPath(const std::vector<std::string>& segments, bool leadingSlash, bool trailingSlash)
{
	m_path.clear();
	bool first = true;
	for (std::vector<std::string>::const_iterator it = segments.begin(); it != segments.end(); ++it)
	{
		if (first)
		{
			first = false;
			if (leadingSlash)
				m_path += '/';
			else if (m_scheme.empty() && (*it).find(':') != std::string::npos)
				m_path.append("./");
		}
		else m_path += '/';
		m_path.append(*it);
	}
	if (trailingSlash) 
		m_path += '/';
}


